#include <utl/vector.h>
#include <utl/async.h>
#include <queue>
#include <iostream>
#include <mutex>
#include <condition_variable>

#ifdef _WINDOWS
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif // !WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif // _WINDOWS
#include <mimalloc.h>

namespace utl
{
	//!!! Global Thread Count
	constexpr u32 thread_max_count{ 16u };

	struct thread_context
	{
		bool life;
#ifdef __GNUC__
		int thread_id;
		pthread_t thread_handle;
#endif // __GNUC__

#ifdef _WINDOWS
		DWORD thread_id;
		HANDLE thread_handle;
#endif // __MSVC__

	};

	//struct async_task_info
	//{
	//	async_task_info(task_base* t, task_completed_pool* _tcp, bool taf) :task{ t }, tcp{ _tcp }, task_auto_free{ taf }
	//	{}
	//	task_base* task;
	//	task_completed_pool* tcp;
	//	bool task_auto_free;
	//};

	struct
	{
		utl::queue<std::pair<utl::shared_ptr<task_base>, task_completed_pool*>> task_queue;
		std::mutex queue_lock;
		std::condition_variable queue_cv;
		thread_context context[thread_max_count];
		bool is_init;
	}global{};


#ifdef __GNUC__
	void* child_main(void* par)
#endif // __GNUC__
#ifdef _WINDOWS
	DWORD child_main(LPVOID par)
#endif // _WINDOWS
	{
		const auto ctx = reinterpret_cast<thread_context*>(par);
		while (ctx->life)
		{

			std::unique_lock ulk{ global.queue_lock };
			while (ctx->life && global.task_queue.empty())
			{
				global.queue_cv.wait(ulk);
			}

			if (!ctx->life)
			{
#ifdef __GNUC__
				return par;
#endif // __GNUC__
#ifdef _WINDOWS
				return 0;
#endif // _WINDOWS
			}


			auto t = global.task_queue.front();
			global.task_queue.pop();
			ulk.unlock();
			t.first->action(&ctx->life);
			if(t.second != nullptr)
			{
				t.second->push(t.first);
			}
		}
#ifdef __GNUC__
		return par;
#endif // __GNUC__
#ifdef _WINDOWS
		return 0;
#endif // _WINDOWS
	}

	//void* alloc_task_element(u32 size)
	//{
	//	return mi_malloc(size);
	//}

	//void free_task_element(void* _ptr)
	//{
	//	mi_free(_ptr);
	//}

	void async(utl::shared_ptr<task_base> t, task_completed_pool* cp)
	{
		std::lock_guard l{ global.queue_lock };
		global.task_queue.push({ t,cp });
		global.queue_cv.notify_all();
	}

	bool async_initialize()
	{
		if (global.is_init)
		{
			return global.is_init;
		}

#ifdef __GNUC__
		for (auto& current_context : global.context)
		{
			current_context.life = true;
			current_context.thread_id = pthread_create(&current_context.thread_handle, nullptr, child_main, &current_context);
		}
#endif // __GNUC__
#ifdef _WINDOWS
		for (auto& current_context : global.context)
		{
			current_context.life = true;
			current_context.thread_handle = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)child_main, &current_context, CREATE_SUSPENDED, &current_context.thread_id);
		}

		for (auto& current_context : global.context)
		{
			ResumeThread(current_context.thread_handle);
		}
#endif // _WINDOWS

		global.is_init = true;
		return global.is_init;

	}

	void async_shutdown()
	{
#ifdef __GNUC__
		utl::vector<pthread_t> temp_handle;
		for (auto& item : global.context)
		{
			item.life = false;
			temp_handle.push_back(item.thread_handle);
		}

		global.queue_cv.notify_all();

		for (auto& item : temp_handle)
		{
			pthread_join(item,/*&cxt*/nullptr);
		}
#endif // __GNUC__
#ifdef _WINDOWS
		utl::vector<HANDLE> temp_handle;
		for (auto& item : global.context)
		{
			item.life = false;
			temp_handle.push_back(item.thread_handle);
		}

		global.queue_cv.notify_all();

		WaitForMultipleObjects((DWORD)temp_handle.size(), temp_handle.data(), TRUE, INFINITE);

		for (auto& item : global.context)
		{
			CloseHandle(item.thread_handle);
		}
#endif // _WINDOWS

		global.is_init = false;
	}

	void task_completed_pool::push(utl::shared_ptr<task_base> fn)
	{
		std::lock_guard l{ locker };
		this->completed_queue.push(fn);
	}

	void task_completed_pool::do_thing(u32 task_count)
	{
		if (!completed_queue.empty())
		{
			std::lock_guard l{ locker };

			for (u32 i{ 0u }; i < task_count && !this->completed_queue.empty(); i++)
			{
				auto tsk{ this->completed_queue.front() };
				this->completed_queue.pop();
				tsk->completed();
			}
		}
	}

	task_base::~task_base()
	{
		//assert(state == TASK_STATE::TASK_STATE_NONE);
	}

	//void task_base::on_completed()
	//{
	//	this->cv.notify_all();
	//}

	//void task_base::run(task_completed_pool* tcp)
	//{
	//	async(this, tcp, false);
	//}

	//void task_base::wait()
	//{
	//	std::unique_lock ul{ this->locked };
	//	this->cv.wait(ul, [this]()
	//	{
	//		return this->state == TASK_STATE::TASK_STATE_NONE;
	//	});
	//}
	
}